/*
 * Copyright (C) 2012  Romain DUBOIS
 * 
 * This file is part of NeMoS - Network and Modular Software
 * 
 * NeMoS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * NeMoS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with NeMoS.  If not, see <http://www.gnu.org/licenses/>.
 */
package nemos.swing.util;

import static nemos.swing.internal.SwingMessage.COMPONENT_DESCRIPTION_INVALID;
import static nemos.swing.internal.SwingMessage.COMPONENT_OBJECT_MISSING;
import static nemos.swing.internal.SwingMessage.COMPONENT_READ_ERROR;
import static nemos.swing.internal.SwingMessage.COMPONENT_TYPE_INVALID;
import static nemos.swing.internal.SwingMessage.INTERNAL_ERROR;
import static nemos.swing.internal.SwingMessage.RESOURCE_MISSING;
import static nemos.swing.internal.SwingMessage.RESOURCE_SET_MISSING;

import java.awt.Container;
import java.awt.Window;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Map;

import javax.swing.JLabel;

import nemos.resources.IResources;
import nemos.resources.IResourcesManager;
import nemos.resources.UnsupportedResourceType;
import nemos.swing.GenerationException;
import nemos.swing.IComponentFactory;
import nemos.swing.ISwingGenerator;
import nemos.swing.internal.SwingMessage;
import nemos.util.log.NemosLogger;

import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.osgi.service.component.ComponentContext;

/**
 * ResourceComponentFactory : The resource-based component factory
 * implementation.
 * 
 * <p>
 * This implementation assumes that at least a part of the component is
 * described in a resource. It relies on the {@link IResourcesManager}
 * functionnality to find the resource and then on the {@link ISwingGenerator}
 * to read the resource and generate the components.
 * <p>
 * 
 * @param <T>
 *            The generated components type
 * 
 * @author Romain DUBOIS
 */
@Service(IComponentFactory.class)
@Component(componentAbstract = true)
public class ResourceComponentFactory<T extends Container> extends AbstractComponentFactory<T> {

    /** The resource default extension */
    private static final String RESOURCE_DEFAULT_EXTENSION = ".xml"; //$NON-NLS-1$
    /** The default resource basename */
    private static final String DEFAULT_BASENAME = "components"; //$NON-NLS-1$

    /** The resources manager */
    @Reference(name = "resources")
    private IResourcesManager mResources;
    /** The Swing generator */
    @Reference(name = "generator", referenceInterface = ISwingGenerator.class)
    private ISwingGenerator mGenerator;

    /** The component name */
    private String mComponentName;
    /** The components map */
    private Map<String, Object> mComponents;
    /** The logger */
    private NemosLogger mLogger;

    /**
     * Constructor.
     * 
     * @param pType
     *            The generated components type
     */
    public ResourceComponentFactory(final Class<T> pType) {
        super(pType);
    }

    /**
     * Start the component.
     * 
     * @param pContext
     *            The component context
     */
    @Activate
    public void start(final ComponentContext pContext) {
        mLogger = new NemosLogger(ResourceComponentFactory.class);
        mLogger.setServiceReference(pContext.getServiceReference());
        mComponentName = (String) pContext.getProperties().get(COMPONENT_TYPE);
    }

    /**
     * {@inheritDoc}
     * 
     * @see nemos.swing.util.AbstractComponentFactory#createComponent(java.util.Map)
     */
    @Override
    public T createComponent(final Map<String, ?> pInitParams) {
        final String lFilePath = getResourceName(pInitParams);
        final String lBaseName = getResourceBaseName(pInitParams);

        try {
            String lErrorCode = null;
            Object[] lErrorParams = null;

            // Find the resource
            URL lUrl = null;
            final IResources<URL> lUrls = mResources.getResources(lBaseName, URL.class);
            if (lUrls == null) {
                lErrorCode = RESOURCE_SET_MISSING;
                lErrorParams = new Object[] { lBaseName };
            } else {
                lUrl = lUrls.getResource(lFilePath);
                if (lUrl == null) {
                    lErrorCode = RESOURCE_MISSING;
                    lErrorParams = new Object[] { lBaseName, lFilePath };
                }
            }

            // If resource is missing, try the default one
            if (lUrl == null) {
                lUrl = getDefaultResource(lBaseName, lFilePath);
            }

            // Still no resource : error
            if (lUrl == null) {
                return createDefault(null, lErrorCode, lErrorParams);
            }

            // Parse resource
            final InputStream lStream = lUrl.openStream();
            try {
                return getType().cast(mGenerator.getComponent(lStream));
            } finally {
                lStream.close();
            }
        } catch (final UnsupportedResourceType e) {
            return createDefault(e, INTERNAL_ERROR);
        } catch (final IOException e) {
            return createDefault(e, COMPONENT_READ_ERROR, mComponentName);
        } catch (final GenerationException e) {
            return createDefault(e, COMPONENT_DESCRIPTION_INVALID, mComponentName);
        } catch (final ClassCastException e) {
            return createDefault(e, COMPONENT_TYPE_INVALID, mComponentName, null);
        }
    }

    /**
     * Get the default resource.
     * 
     * @param pBaseName
     *            The resource base name
     * @param pName
     *            The resource name
     * @return The default resource
     */
    protected URL getDefaultResource(final String pBaseName, final String pName) {
        return getClass().getResource('/' + pBaseName + '/' + pName + RESOURCE_DEFAULT_EXTENSION);
    }

    /**
     * Load all identified components described in the resource.
     * 
     * @param pInitParams
     *            The initial parameters
     * @throws GenerationException
     *             If an error occurred during components generation
     */
    protected void loadComponents(final Map<String, ?> pInitParams) throws GenerationException {
        final String lFilePath = getResourceName(pInitParams);
        final String lBaseName = getResourceBaseName(pInitParams);

        try {
            // Get the resources
            final IResources<URL> lUrls = mResources.getResources(lBaseName, URL.class);
            if (lUrls == null) {
                throw new GenerationException(getText(false, RESOURCE_SET_MISSING, lBaseName));
            }

            // Get the resource URL
            final URL lUrl = lUrls.getResource(lFilePath);
            if (lUrl == null) {
                throw new GenerationException(
                        getText(false, RESOURCE_MISSING, lBaseName, lFilePath));
            }

            // Parse resource
            final InputStream lStream = lUrl.openStream();
            try {
                mComponents = mGenerator.getComponents(lStream);
            } finally {
                lStream.close();
            }
        } catch (final UnsupportedResourceType e) {
            throw new GenerationException(getText(false, INTERNAL_ERROR), e);
        } catch (final IOException e) {
            throw new GenerationException(getText(false, COMPONENT_READ_ERROR, mComponentName), e);
        }
    }

    /**
     * Test if a component is available.
     * 
     * @param pId
     *            The required ID
     * @param pType
     *            The required type
     * @return If the identified object exists and has a valid type
     */
    protected boolean componentExists(final String pId, final Class<?> pType) {
        if (mComponents != null) {
            final Object lObject = mComponents.get(pId);
            return pType.isInstance(lObject);
        }
        return false;
    }

    /**
     * Get a component from the generated objects map.
     * 
     * @param pId
     *            The required ID
     * @param pType
     *            The required type
     * @return The identified object if it exists and has a valid type
     * @throws GenerationException
     *             If the object does not exists or has an invalid type
     */
    protected <C> C getComponent(final String pId, final Class<C> pType) throws GenerationException {
        Object lObject = null;
        if (mComponents != null) {
            lObject = mComponents.get(pId);
            if (pType.isInstance(lObject)) {
                return pType.cast(lObject);
            }
        }

        if (lObject == null) {
            throw new GenerationException(mLogger.getTranslator().getMessage(
                    COMPONENT_OBJECT_MISSING, mComponentName, pId));
        }
        throw new GenerationException(mLogger.getTranslator().getMessage(COMPONENT_TYPE_INVALID,
                mComponentName, pId));
    }

    /**
     * Get the formatted and displayable text.
     * 
     * @param pUi
     *            If the text is for UI display
     * @param pMessageCode
     *            The message code
     * @param pParams
     *            The message parameters
     * @return The text
     */
    protected String getText(final boolean pUi, final String pMessageCode, final Object... pParams) {
        if (pUi) {
            return mLogger.getTranslator().getLabel(pMessageCode, pParams);
        }
        return mLogger.getTranslator().getMessage(pMessageCode, pParams);
    }

    /**
     * Get the resource base name.
     * 
     * @param pInitParams
     *            The component initialization parameters
     * @return The base name to use to collect the resource
     */
    protected String getResourceBaseName(
            @SuppressWarnings("unused") final Map<String, ?> pInitParams) {
        return DEFAULT_BASENAME;
    }

    /**
     * Get the resource name.
     * 
     * @param pInitParams
     *            The component initialization parameters
     * @return The resource name
     */
    protected String getResourceName(@SuppressWarnings("unused") final Map<String, ?> pInitParams) {
        return mComponentName;
    }

    /**
     * Create a default component.
     * 
     * @param pException
     *            The previously occured exception (can be {@code null})
     * @param pMessageCode
     *            The code of the message to display
     * @param pParams
     *            The message parameters
     * @return The default component
     */
    protected T createDefault(final Throwable pException, final String pMessageCode,
            final Object... pParams) {
        String lMessageCode = pMessageCode;
        Object[] lParams = pParams;
        if (lMessageCode == null) {
            lMessageCode = SwingMessage.COMPONENT_DESCRIPTION_INVALID;
            lParams = new Object[] { mComponentName };
        }

        mLogger.error(lMessageCode, pException, lParams);

        T lDefault = null;
        try {
            lDefault = getType().newInstance();
            lDefault.add(new JLabel(getText(true, lMessageCode, lParams)));
            if (lDefault instanceof Window) {
                ((Window) lDefault).pack();
            }

        } catch (final Exception e) {
            mLogger.error(SwingMessage.COMPONENT_DEFAULT_FAILED, e, mComponentName);
        }
        return lDefault;
    }

    /**
     * Get the logger.
     * 
     * @return The logger
     */
    protected NemosLogger getLogger() {
        return mLogger;
    }

    /**
     * Get the resources manager.
     * 
     * @return The resources manager
     */
    public IResourcesManager getResources() {
        return mResources;
    }

    /**
     * Bind the resources manager.
     * 
     * @param pResources
     *            The resources manager to bind
     */
    public void bindResources(final IResourcesManager pResources) {
        mResources = pResources;
    }

    /**
     * Unbind the resources manager.
     * 
     * @param pResources
     *            The resources manager to unbind
     */
    public void unbindResources(@SuppressWarnings("unused") final IResourcesManager pResources) {
        mResources = null;
    }

    /**
     * Bind the swing generator.
     * 
     * @param pGenerator
     *            The generator to bind
     */
    public void bindGenerator(final ISwingGenerator pGenerator) {
        mGenerator = pGenerator;
    }

    /**
     * Unbind the swing generator.
     * 
     * @param pGenerator
     *            The generator to unbind
     */
    public void unbindGenerator(@SuppressWarnings("unused") final ISwingGenerator pGenerator) {
        mGenerator = null;
    }
}
